
// 每次都判断兼容: 利用惰性思想进行浏览器兼容处理
function getXhr() {
    let arr = [
        function () {
            return new ActiveXObject("Microsoft.XMLHTTP");
        },
        function () {
            return new ActiveXObject("Msxml2.XMLHTTP");
        },
        function () {
            return new ActiveXObject("Msxml3.XMLHTTP");
        },
        function () {
            return new XMLHttpRequest()
        }
    ];
    let flag = false;
    let xhr = null;
    for (let i = 0; i < arr.length; i++) {
        let tempXhr = arr[i];
        try {
            //如果不报错那么就说明当前浏览器支持这个方法, 那么我们把getXhr重写
            xhr = tempXhr();
            flag = true;
            //重写getXhr之后,下次再调用getXhr就不会再执行这些循环和判断了
            getXhr = tempXhr;
            break;
        } catch (e) {
            //如果浏览器报错会执行以下代码, 继续循环下一次,把数组中下一个方法执行
            continue;
        }
    }
    //如果循环执行完毕后 flag还是false 那么说明浏览器四个方法都不兼容
    if (!flag) {
        throw new Error("浏览器不支持 ajax")
    }
}

//柯里化(颗粒化)函数 惰性函数思想
//高阶函数编程思想


!function(){

    //x.ajax({
    //    url:"",// 请求的路径
    //    data:{},//发送数据
    //    cache:false,//是否缓存
    //    headers:{},//自定义请求首部
    //    dataType:"json",
    //    async:true,
    //    timeout:3000,
    //    success:function(){},
    //    error:function(){},
    //    method:"post"//使用的http方法
    //});

    /**
     * ajax逻辑
     * @param {object} options 参数列表
     */
    function ajax(options){
        if(!tools.getType(options,"Object")){//判断参数是否合法
            throw new TypeError("参数类型错误");
        }

        // ajax第一步:获取ajax对象
        var xhr = tools.getXHR();


        if(options.data){//如果data有值,直接将data格式化为uriString格式
            options.data = tools.encodeToURIString(options.data)
        }


        //如果是get系,把data拼接到url后面
        if(/^(get|delete|head)$/ig.test(options.method) && options.data){
            options.url = tools.padString(options.url,options.data);
            options.data = "";
        }
        //判断是否走缓存,往url后面加一个随机数
        if(options.cache === false){
            options.url = tools.padString(options.url,"_="+Math.random());//用下划线是为了防止和其他param冲突,使用_当key.因为没人用_当key
        }


        //ajax第二步 :执行open方法
        xhr.open(options.method,
            options.url,
            options.async,
            options.username,
            options.password);

        if(tools.getType(options.headers,"Object")){
            for(var name in options.headers){
                if(!(options.headers.hasOwnProperty(name))){
                    continue;
                }
                xhr.setRequestHeader(name,options.headers[name])
            }
        }
        //ajax第三步:注册onreadystatechange事件
        xhr.onreadystatechange = function (){
            if(xhr.readyState === 4){//判断http事务是否完成
                if(/^2\d{2}$/.test(xhr.status)){//判断状态码是否为2xx
                    var responseText = xhr.responseText;
                    if(options.dataType === "json"){//是否需要把服务器返回的数据格式化为json对象
                        try{//因为不合法json字符串强制格式化为json对象会出现JS异常.所以使用try catch包住
                            tools.JSONParse(responseText);
                        }catch(ex){
                            options.error && options.error(ex);//如果格式化json对象报错.直接执行error方法.
                            return;//强制终止后续逻辑
                        }
                    }
                    //执行成功逻辑
                    options.success && options.success(responseText);
                }else if(/^(4|5)\d{2}$/.test(xhr.status)){//4xx或5xx 直接抛出错误
                    options.error && options.error(xhr.status);
                }
            }
        };

        //处理超时逻辑
        if(tools.getType(options.timeout,"Number")&& options.timeout > 0) {
            if ("ontimeout" in xhr) {//如果xhr对象包含ontimeout属性,超时的话,会强制终止该http事物;
                //timeout表示xhr对象的超时毫秒数
                xhr.timeout = options.timeout;//xhr.timeout 原始ajax自带的超时
                //ontimeout表示过了timeout毫秒.http事务还未完成,则触发timeout事件
                xhr.ontimeout = function () {
                    options.error && options.error("timeout");
                }
            } else {
                setTimeout(function () {
                    if (xhr.readyState !== 4) {//超时时间已到,但是http事物还未完成
                        //强制终止http事务
                        xhr.abort();
                        options.error && options.error("timeout");
                    }
                }, options.timeout)
            }
            xhr.send(options.data);
        }
    }











    var tools = {
        /**
         * 获取ajax对象
         */
        getXHR:(function(){
            if(window.ActiveXObject){
                return function(){
                    return new ActiveXObject("Microsoft.XMLHTTP");
                }
            }
            return function(){
                return new XMLHttpRequest();
            }
        })(),

        /**
         * 获取数据类型
         * @param {*} obj  obj需要判断类型的数据
         * @param {string} type 数据类型
         * @returns {boolean}
         */
        getType:function(obj,type){
            return Object.prototype.toString.call(obj) === "[object"+type+"]";
        },

        /**
         * 把对象格式化为URIString格式
         * @param {*} data 需要格式化的数据
         * @returns {string}
         */
        encodeToURIString:function(data){
            if(typeof data === "string"){
                return data;
            }
            if(this.getType(data,"Object")){
                var arr = [];
                for(var n in data){
                    if(!(data.hasOwnProperty(n))) econtinue;
                    arr.push(encodeURIComponent(n) + "=" +encodeURIComponent(data[n]))
                }
                return arr.join("&")
            }
            return "";
        },

        /**
         * 往url后面拼接参数
         * @param {string} url url
         * @param {string} params 参数
         */
        padString:function(url,params){
            params = this.encodeToURIString(params);
            if(!params){
                return url;
            }
            var hasSearch = /\?/.test(url);
            if(hasSearch){//说明url中已经含有参数了
                return url += "&" + params;
            }else{//说明url中不含有参数
                return url += "?" + params
            }

        },

        /**
         * 格式化json字符串为一个json对象
         * @param {string} jsonString json字符串
         * @returns {*} json对象
         * @constructor
         */
        JSONParse: function (jsonString) {
            if(this.getType(jsonString,"Object")){
                return jsonString;
            }
            if(window.JSON){
                return JSON.parse(jsonString);
            }
            return eval("(" + jsonString + ")")
        }
    };

    this.x = {};

}();